﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace SharpShare.Utilities.NodeTracking {
    public enum TokenAssignmentCollisionPolicy {
        ThrowException,
        Invalidate
    }

    public sealed class TokenAssigner<TToken, TItem>  {
        private object _lock = new object();
        private Dictionary<TItem, TToken> _assignmentsByItem;
        private Dictionary<TToken, TItem> _assignmentsByToken;

        public TokenAssigner(
            ITokenGenerator<TToken> tokenGenerator,
            TokenAssignmentCollisionPolicy tokenCollisionPolicy = TokenAssignmentCollisionPolicy.ThrowException,
            IEqualityComparer<TItem> itemComparer = null) {

            if (tokenGenerator == null) throw new ArgumentNullException("tokenGenerator");
            this.TokenGenerator = tokenGenerator;
            this.TokenCollisionPolicy = tokenCollisionPolicy;
            this.ItemComparer = (itemComparer ?? EqualityComparer<TItem>.Default);
            _assignmentsByItem = new Dictionary<TItem, TToken>(this.ItemComparer);
            _assignmentsByToken = new Dictionary<TToken, TItem>(tokenGenerator.Comparer ?? EqualityComparer<TToken>.Default);
        }

        public ITokenGenerator<TToken> TokenGenerator { get; private set; }
        public TokenAssignmentCollisionPolicy TokenCollisionPolicy { get; private set; }
        public IEqualityComparer<TItem> ItemComparer { get; private set; }

        public bool TryGet(TToken token, out TItem item) {
            if (token == null) throw new ArgumentNullException("token");

            lock (_lock) {
                return _assignmentsByToken.TryGetValue(token, out item);
            }
        }
        public TItem Get(TToken token) {
            TItem item;
            if (!TryGet(token, out item))
                return default(TItem);
            return item;
        }
        public TToken Get(TItem item) {
            if (item == null) throw new ArgumentNullException("item");

            lock (_lock) {
                TToken token;
                if (!_assignmentsByItem.TryGetValue(item, out token)) {
                    token = this.TokenGenerator.Next();
                    if (_assignmentsByToken.ContainsKey(token)) {
                        switch (this.TokenCollisionPolicy) {
                            case TokenAssignmentCollisionPolicy.Invalidate:
                                Invalidate(token);
                                break;
                            case TokenAssignmentCollisionPolicy.ThrowException:
                                throw new TokenAssignmentCollisionException();
                        }
                    }

                    _assignmentsByToken[token] = item;
                    _assignmentsByItem[item] = token;
                }
                return token;
            }
        }

        public bool Invalidate(TToken token) {
            lock (_lock) {
                if (!_assignmentsByToken.ContainsKey(token))
                    return false;

                TItem item = _assignmentsByToken[token];

                _assignmentsByToken.Remove(token);
                _assignmentsByItem.Remove(item);

                return true;
            }
        }

        public bool Invalidate(TItem item) {
            lock (_lock) {
                TToken token;
                if (!_assignmentsByItem.TryGetValue(item, out token))
                    return false;
                Invalidate(token);
                return true;
            }
        }
    }

    public class TokenAssignmentCollisionException : SystemException {
        public TokenAssignmentCollisionException()
            : base("The token generator returned a token which collides with an existing value.") {

        }
    }
}
